import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
  ArticleAdminIndexDto,
  ArticleAdminUpdateDto,
  ArticleCreateDto,
  ArticleIndexNormalDto,
  ArticleMineDto,
  ArticleUpdateDto,
} from './article.dto';
import { ArticleModel } from './article.model';
import { UserModel } from '@/module/user/user.model';
import { ArticleExtModel } from '@/module/article-ext/article-ext.model';
import { MessageService } from '@/module/message/message.service';
import { ArticleNormalCategory, ArticleStatus, ArticleType, MessageContentType, UserType, } from 'common/utils/data';
import { BizFailedException } from '@/common/biz-failed/biz-failed.exception';
import { BizFailedCodeEnum } from '@/common/biz-failed/biz-failed.enum';
import { UpdateArticleStatusByAdmin, UpdateArticleStatusByUser, } from './article.enum';
import { omitUndefined } from '@/common/utils/utils';
import { Transactional } from "typeorm-transactional";
import { getImgListFormRichText } from "common/utils/utils";

@Injectable()
export class ArticleService {
  constructor(
    @InjectRepository(ArticleModel)
    private readonly articleModelRepository: Repository<ArticleModel>,
    @InjectRepository(ArticleExtModel)
    private readonly articleExtModelRepository: Repository<ArticleExtModel>,
    private readonly messageService: MessageService,
  ) {}

  async indexNormal(query: ArticleIndexNormalDto) {
    const queryBuilder = this.articleModelRepository
      .createQueryBuilder('article')
      .leftJoinAndSelect('article.user', 'user')
      .select([
        'article.uuid',
        'article.isStick',
        'article.categories',
        'article.title',
        'article.publishTime',
        'article.keywords',
        'article.thumbnails',
        'article.thumbnailsByContent',
        'article.commentCount',
        'article.desc',
        'article.descByContent',
        'user.uuid',
        'user.avatar',
        'user.name',
        'user.titleCertification',
      ])
      .andWhere(`(article.status = '${ArticleStatus.PUBLISH}')`)
      .andWhere(`(article.type = '${ArticleType.NORMAL}')`);

    if (query.category) {
      queryBuilder.andWhere('(article.categories LIKE :category)', {
        category: `%${query.category}%`,
      });
    }

    if (query.keyword) {
      queryBuilder.andWhere('(article.title ILIKE :keyword OR article.keywords ILIKE :keyword)', {
        keyword: `%${query.keyword}%`,
      });
    }

    query.wrapQueryBuilder(queryBuilder,'article', {
      firstOrderBy: (origin)=>{
        return {
          'article.isStick': 'DESC',
          ...origin,
        }
      }
    })

    return queryBuilder.getManyAndCount();
  }

  async indexMine(query: ArticleMineDto, user: UserModel) {
    const qb = this.articleModelRepository
      .createQueryBuilder('article')
      .where(`(article.userUuid = '${user.uuid}')`)
      .andWhere(`(article.type = '${ArticleType.NORMAL}')`)
      .select([
        'article.id',
        'article.uuid',
        'article.categories',
        'article.title',
        'article.desc',
        'article.commentCount',
        'article.status',
        'article.isStick',
        'article.publishTime',
        'article.updateTime',
      ])

    if (query.status) {
      qb.andWhere('(article.status = :status)', {
        status: query.status,
      });
    }

    if (query.title) {
      qb.andWhere('(article.title LIKE :title)', {
        title: `%${query.title}%`,
      });
    }

    query.wrapQueryBuilder(qb,'article')

    return qb.getManyAndCount();
  }

  async showMine(uuid: string, user: UserModel) {
    return this.articleModelRepository
      .createQueryBuilder('article')
      .where('(article.uuid = :uuid)', { uuid })
      .andWhere(`(article.userUuid = '${user.uuid}')`)
      .leftJoinAndSelect('article.ext', 'ext')
      .select([
        'article.uuid',
        'article.categories',
        'article.title',
        'article.keywords',
        'article.desc',
        'article.thumbnails',
        'article.status',
        'article.publishTime',
        'ext.content',
        'ext.contentRaw',
      ])
      .getOne();
  }

  @Transactional()
  async createMine(body: ArticleCreateDto, user: UserModel) {
    const {
      content,
      contentRaw,
      ...restBody
    } = body;
    const article = await this.articleModelRepository.save({
      ...restBody,
      userUuid: user.uuid,
      thumbnailsByContent: content ? getImgListFormRichText(
        content,
        3,
      ) : [],
      descByContent: content ? this.getPlainText(content, 1000) : '',
      categories: user.userType === UserType.ADMIN ? restBody.categories : [ArticleNormalCategory.HIFI_DISCUSS],
    })
    await this.articleExtModelRepository.save({
      articleUuid: article.uuid,
      content,
      contentRaw,
    })
    return {
      uuid: article.uuid,
    };
  }

  @Transactional()
  async updateMine(
    body: ArticleUpdateDto,
    uuid: string,
    user: UserModel,
  ): Promise<void> {
    const article = await this.articleModelRepository.findOne({
      where: {
        uuid,
        userUuid: user.uuid,
      },
    });
    if (!article) {
      throw new BizFailedException(BizFailedCodeEnum.ARTICLE_NOT_EXISTS);
    }
    const updateArticleParam: ArticleUpdateDto = {
      status: ArticleStatus.DRAFT as unknown as UpdateArticleStatusByUser, // 默认修改文章就置为了DRAFT
      ...omitUndefined({
        ...body,
        thumbnailsByContent: body.content ? getImgListFormRichText(
         body.content,
          3,
        ) : [],
        descByContent: body.content ? this.getPlainText(body.content, 1000) : '',
        categories: body.categories ? (user.userType === UserType.ADMIN ? body.categories : [ArticleNormalCategory.HIFI_DISCUSS]) : undefined,
      }),
    };
    const { content, contentRaw, ...restUpdateArticleParam } =
      updateArticleParam;

    await this.articleModelRepository.update({
      uuid,
    },
      restUpdateArticleParam as unknown as ArticleModel,
    );
    if (content || contentRaw) {
      await this.articleExtModelRepository.update(
        {
          articleUuid: article.uuid,
        },
        {
          content,
          contentRaw,
        },
      );
    }
  }

  async destroyMine(uuid: string, user: UserModel): Promise<void> {
    await this.articleModelRepository.delete({
      uuid,
      userUuid: user.uuid,
    });
  }

  async showNormalArticle(uuid: string) {
    const article = await this.articleModelRepository.findOne({
      where: {
        uuid,
        status: ArticleStatus.PUBLISH,
      },
    });
    if (!article) {
      throw new BizFailedException(BizFailedCodeEnum.ARTICLE_NOT_EXISTS);
    }

    return this.articleModelRepository
      .createQueryBuilder('article')
      .where(`article.status = '${ArticleStatus.PUBLISH}'`)
      .andWhere('article.uuid = :uuid', { uuid })
      .leftJoinAndSelect('article.user', 'user')
      .leftJoinAndSelect('article.ext', 'ext')
      .select([
        'article.uuid',
        'article.title',
        'article.keywords',
        'article.desc',
        'article.thumbnails',
        'article.publishTime',
        'article.type',
        'user.uuid',
        'user.name',
        'user.avatar',
        'user.title',
        'user.signature',
        'user.titleCertification',
        'ext.content'
      ])
      .getOne();
  }

  async recommend(uuid: string){
    const article = await this.articleModelRepository.findOne({
      where: {
        uuid,
        status: ArticleStatus.PUBLISH,
      },
    });
    if (!article) {
      throw new BizFailedException(BizFailedCodeEnum.ARTICLE_NOT_EXISTS);
    }
    return this.articleModelRepository
      .createQueryBuilder('article')
      .select([
        'article.uuid',
        'article.title',
        'article.thumbnails',
        'article.publishTime',
        'article.keywords',
      ])
      .where(`(article.status = '${ArticleStatus.PUBLISH}')`)
      .andWhere(`(article.type = '${article.type}')`)
      .andWhere(`(article.uuid != '${article.uuid}')`)
      .take(6)
      .orderBy('random()')
      .getMany();
  }

  // ----
  async adminIndex(query: ArticleAdminIndexDto) {
    const qb = this.articleModelRepository
      .createQueryBuilder('article')
      .leftJoinAndSelect('article.user', 'user')
      .leftJoinAndSelect('article.evaluation', 'evaluation')
      .select([
        'article.uuid',
        'article.title',
        'article.isStick',
        'article.thumbnails',
        'article.categories',
        'article.keywords',
        'article.status',
        'article.desc',
        'article.publishTime',
        'article.createTime',
        'article.updateTime',
        'user.uuid',
        'user.name',
        'user.mobile',
      ])
      .limit(query.getPageSize())
      .offset(query.getOffset())
      .orderBy({
        'article.isStick': 'DESC',
        'article.publishTime': 'DESC',
        'article.id': 'DESC',
      })
      .where('(article.type = :type)', { type: ArticleType.NORMAL });

    if (query.status) {
      qb.andWhere('(article.status = :status)', { status: query.status });
    } else {
      qb.andWhere(
        `(article.status IN ('${ArticleStatus.PUBLISH}','${ArticleStatus.REVIEW}'))`,
      );
    }

    if (query.category) {
      qb.andWhere('(article.categories LIKE :category)', {
        category: `%${query.category}%`,
      });
    }

    if (query.title) {
      qb.andWhere('(article.title LIKE :title)', { title: `%${query.title}%` });
    }

    return qb.getManyAndCount();
  }

  async adminShowNormal(uuid: string) {
    const article = await this.articleModelRepository.findOne({
      where: {
        uuid,
      },
    });
    if (!article) {
      throw new BizFailedException(BizFailedCodeEnum.ARTICLE_NOT_EXISTS);
    }
    const qb = this.articleModelRepository
      .createQueryBuilder('article')
      .andWhere('article.uuid = :uuid', { uuid })

    return qb
      .leftJoinAndSelect('article.ext', 'ext')
      .leftJoinAndSelect('article.user', 'user')
      .select([
        'article.uuid',
        'article.title',
        'article.keywords',
        'article.desc',
        'article.thumbnails',
        'article.publishTime',
        'article.categories',
        'article.status',
        'article.type',
        'user.uuid',
        'user.name',
        'user.mobile',
        'user.avatar',
        'user.title',
        'user.titleCertification',
        'user.signature',
        'ext.content',
      ])
      .getOne();
  }

  @Transactional()
  async adminUpdate(body: ArticleAdminUpdateDto, uuid: string): Promise<void> {
    const article = await this.articleModelRepository.findOne({
      where: {
        uuid,
      },
    });
    if (!article) {
      throw new BizFailedException(BizFailedCodeEnum.ARTICLE_NOT_EXISTS);
    }
    const { rejectReason, ...restBody } = body;
    const param: Omit<ArticleAdminUpdateDto, 'rejectReason'> & {
      publishTime?: number;
      desc?: string;
      commentTime?: number;
    } = { ...restBody };
    if (
      article.status !== ArticleStatus.PUBLISH &&
      body.status === UpdateArticleStatusByAdmin.PUBLISH
    ) {
      const now = Date.now();

      if (article.publishTime === null) {
        // 第一次发布的时候才赋值发布时间
        param.publishTime = now;
      }

      if (article.commentTime === null) {
        // 第一次发布的时候刷新最新评论时间
        param.commentTime = now;
      }

    }
    await this.articleModelRepository.update(
      {
        uuid,
      },
      omitUndefined(param),
    );
    if (body.status === UpdateArticleStatusByAdmin.PUBLISH) {
      this.messageService.create({
        contentType: MessageContentType.ARTICLE_BE_PUBLISH,
        contentTypeOption: {
          articleTitle: article.title,
        },
        userUuid: article.userUuid,
      });
    } else if (body.status === UpdateArticleStatusByAdmin.REVIEW) {
      this.messageService.create({
        contentType: MessageContentType.ARTICLE_BE_REVIEW,
        contentTypeOption: {
          articleTitle: article.title,
        },
        userUuid: article.userUuid,
      });
    } else if (body.status === UpdateArticleStatusByAdmin.REJECT) {
      this.messageService.create({
        contentType: MessageContentType.ARTICLE_BE_REJECT,
        contentTypeOption: {
          articleTitle: article.title,
          rejectReason,
        },
        userUuid: article.userUuid,
      });
    }
    if (body.isStick === 1) {
      this.messageService.create({
        contentType: MessageContentType.ARTICLE_BE_STICK,
        contentTypeOption: {
          articleTitle: article.title,
        },
        userUuid: article.userUuid,
      });
    } else if (body.isStick === 0) {
      this.messageService.create({
        contentType: MessageContentType.ARTICLE_BE_UNSTICK,
        contentTypeOption: {
          articleTitle: article.title,
        },
        userUuid: article.userUuid,
      });
    }
  }

  // 从富文本摘取纯文本
  private getPlainText(richText: string, length: number) {
    return richText
      .replace(/<[^>]+>/g, '')
      .replace(/ |\n|\r\n|&nbsp;|&ensp;/g, '')
      .replace(/[a-zA-Z]&#7([6-7][0-9]);/g, ' ')
      .replace(/&([a-z]{2,5}|#[0-9]{1,6});/g, ' ')
      .substring(0, length);
  }

}
